/** 基于发布订阅模式的一个消息调度中心
 * 
 * 使用方法：
 *  0、 引入msgCenter
 * import msgCenter from "MessageCenter.js"
 *  1、 设置监听事件
 * msgCenter.on("MESSAGE_ID",function(params){ },target) 
 *  2、 触发监听事件
 * msgCenter.emit("MSEEAGE_ID",params)
 *  3、 移除监听
 * msgCenter.remove("MESSAGE_ID")
 *  4、 移除target中所有的监听
 * msgCenter.removeTarget(target)
 * 
 */
 let msgCenter;
 let msgPending = false
 //MARK: Target 中绑定方法
 
 function bindTargetMessageId(message_id){
     if(!message_id) return
     let target = this
     if(!target[Symbol.for("center_message_ids")]){
         target[Symbol.for("center_message_ids")] = new Set()
     }
 
     target[Symbol.for("center_message_ids")].add(message_id)
 }
 
 
 function unbindTargetMessageId(){
     let target = this
     let set = new Set(target[Symbol.for("center_message_ids")])
     target[Symbol.for("center_message_ids")] = null
     return set
 }
 
 //  微任务函数 工厂
 function microFunction(){
     let p = Promise.resolve()
 
     return function(fnc) {
         let target = this
         let arg = arguments
         p.then(function(){
             fnc.apply(target, arg)
         })
     }
 }
 
 let nextTickFunc = microFunction()
 
 
 //MARK: MessageCenter
 let MessageCenter = function(){
     
     if(!(this instanceof MessageCenter)){
         console.error("MessageCenter 是一个构造函数，请使用new 方法构建")
         return
     }
 
     // 单例子模式
     if(msgCenter){
         return msgCenter
     }
     
     this.msgQueue = []
 
     this.eventMap = new Map()
 }
 
 
 //MARK: Target MessageCenter相关的方法
 /**
  * 设置监听方法
  * @param {string} message_id 方法对应的ID 字符串类型
  * @param {function} fn 对应的回调函数
  * @param {object} target 调用对象
  * @returns 
  */
 MessageCenter.prototype.on = function(message_id, fn, target) {
     // 如果message_id 不是字符串直接报错
     if(typeof(message_id) !== "string"){
         console.error("msgCenter on 操作 message_id 必须是字符串类型")
         return
     }
 
     if(typeof(fn) !== "function"){
         console.error("msgCenter on 操作 fn 必须是函数类型")
         return
     }
     
     if(!target){
         console.error("msgCenter on 操作 target 必须传")
         return
     }
 
     // 绑定MessageId中的所有事件名称
     bindTargetMessageId.call(target, message_id)
 
     let set
     if(this.eventMap.has(message_id)){
         set = this.eventMap.get(message_id)
     }else{
         set = new Set()
     }
 
     set.add({fn,target})
 
     this.eventMap.set(message_id, set)
 }
 
 /**
  * 触发监听事件
  * @param {string} message_id 事件名
  * @param {object|any} params 参数
  * @param {boolean} immediate 是否立即执行，false 表示 会在微任务队列中执行。true表示直接通知 默认是false
  * @returns void
  */
 MessageCenter.prototype.emit = function(message_id, params, immediate = false){
     if(typeof(message_id) !== "string"){
         console.error("msgCenter emit 操作 message_id 必须是字符串类型")
         return
     }
 
     if(!this.eventMap.has(message_id)){
         console.warn("msgCenter 中未注册对应是监听事件");
         return 
     }
    
     if(immediate){
         let set = this.eventMap.get(message_id)
         set.forEach( e => {
             let {fn,target} = e
             fn.call(target, params)
         });
     }else{
         // TODO: 如果是延时到执行微任务队列时机
         // 将事件和参数 放入模拟的微任务队列中，再由Promise执行
         this.msgQueue.push({ message_id, params })
 
         if(!msgPending){
             msgPending = true
 
             nextTickFunc(()=>{
                 msgPending = false;
                 let copis = this.msgQueue.slice(0)
                 this.msgQueue.length = 0
                 copis.forEach(({ message_id, params })=>{
                     let set = this.eventMap.get(message_id)
                     set.forEach( e => {
                         let {fn,target} = e
                         fn.call(target, params)
                     });
                 })
             })
         }
     }
 }
 
 /**
  * 移除 message_id 对应的监听事件
  * @param {string} message_id 移除message对应的事件名 
  * @returns void
  */
 MessageCenter.prototype.remove = function(message_id) {
     if(typeof(message_id) !== "string"){
         console.error("msgCenter on 操作 message_id 必须是字符串类型")
         return
     }
 
     if(this.eventMap.has(message_id)){
         let set = this.eventMap.get(message_id)
         set.clear()
     }
     
     this.eventMap.delete(message_id)
 }
 
 /**
  * 移除 target目标下的所有监听事件
  * @param {object} target 目标
  */
 MessageCenter.prototype.removeTarget = function(target) {
     let messageIds = unbindTargetMessageId.call(target)
     console.warn("removeTarget",messageIds);
     messageIds.forEach(message_id=>{
         if(this.eventMap.has(message_id)){
             let set = this.eventMap.get(message_id)
             set.forEach(e=>{
                 if(e.target == target){
                     set.delete(e)
                 }
             })
         }
     })
 }
 
 msgCenter = new MessageCenter()
 
 export default msgCenter;